package com.ifonly.ar.core;

import com.ifonly.ar.dialect.Dialect;
import com.ifonly.ar.exections.BuildTableException;
import com.ifonly.ar.providers.IDataSourceProvider;
import com.ifonly.ar.proxy.ConnectionSqlReportHandler;
import com.ifonly.ar.proxy.ProxyFactory;
import com.ifonly.ar.utils.DbUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author ifonly
 * @version 1.0 2015-12-03 14:11
 * @since JDK 1.6
 */
public class Db {

    private static IDataSourceProvider dataSourceProvider;
    private static Dialect dialect;

    private DataSource ds;

    private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>();

    private Map<String, Class<? extends ARModel>> tableMapping = new HashMap<String, Class<? extends ARModel>>();
    private ARTableMapper arTableMapper = ARTableMapper.getInstance();

    private Db(DataSource ds) {
        this.ds = ds;
    }

    private static class InstanceHolder {
        private static Db instance = new Db(dataSourceProvider.getDataSource());
    }

    public static void init(IDataSourceProvider dataSourceProvider, Dialect dialect) {
        Db.dataSourceProvider = dataSourceProvider;
        Db.dialect = dialect;
    }


    public synchronized static Db getInstance() {
        if (dataSourceProvider == null) {
            throw new RuntimeException("Please init the Db");
        }
        return InstanceHolder.instance;
    }

    public Dialect getDialect() {
        return dialect;
    }

    private Connection getConnection() throws SQLException {
        Connection connection = ds.getConnection();
        if (Config.DEV) {
            connection = ProxyFactory.newProxyInstance(connection, new ConnectionSqlReportHandler(connection));
        }
        return connection;
    }

    public synchronized Connection getThreadLocalConnection() throws SQLException {
        Connection connection = connectionThreadLocal.get();
        if (connection == null) {
            connection = getConnection();
            connectionThreadLocal.set(connection);
        }
        return connection;
    }

    public void closeConnection(Connection conn) throws SQLException {
        if (connectionThreadLocal.get() == null) {
            DbUtils.close(conn);
        }
    }

    public void closeAndRemoveThreadLocalConnection() throws SQLException {
        Connection connection = connectionThreadLocal.get();
        if (connection != null) {
            DbUtils.close(connection);
        }
        connectionThreadLocal.remove();
    }

    public void removeThreadLocalConnection() {
        connectionThreadLocal.remove();
    }

    public Db addTableARModel(String tableName, Class<? extends ARModel> model) {
        tableMapping.put(tableName, model);
        return this;
    }

    public void buildTableARModels() {
        Connection conn = null;
        try {
            conn = getConnection();
            TableBuilder builder = new TableBuilder(conn);
            Set<String> keySet = tableMapping.keySet();
            for (String key : keySet) { //The key is the tableName
                Class<? extends ARModel> model = tableMapping.get(key);
                String sql = dialect.initTableSql(key);

                Table table = builder.build(key, model, sql);

                arTableMapper.set(model, table);
            }
        } catch (SQLException e) {
            throw new BuildTableException("build table failed", e);
        } finally {
            try {
                closeConnection(conn);
            } catch (SQLException e) {
                /* ignore */
            }
        }
    }

}
